package message;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;


/**
 * This class handles all the communication via UDP.
 */
public class MessageHandler implements Runnable {
	
	private static final boolean DEBUG = false;

	private static final int UDP_BUFFER_SIZE = 65535;
	private int  port;
	private MessageActions action;
	private DatagramChannel channel;

	/**
	 * Common constructor of the MessageHandler class. The port is hidden from
	 * the user. Intern a free port > 9999 is used.
	 * 
	 * @param a An implementation of the MessageActions interface, where the
	 * 			behavior to incoming messages is implemented.
	 */
	public MessageHandler(MessageActions a) {
		this.action = a;
		this.port = 10000;
		try {
			channel = DatagramChannel.open();
		} catch (IOException e) {
			System.err.println("Could not open channel in MessageHandler!");
		}
	}
	
	public MessageHandler(MessageActions action, int port) {
		this.action = action;
		this.port = port;
		try {
			channel = DatagramChannel.open();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		while (!bindToPort() && this.port <= 65535) {
			this.port++;
		}
		if (this.port > 65535) {
			System.err.println("Could not bind to port in MessageHandler!");
			return;
		}
		action.setPort(port);
		this.listen();
	}
	
	public int getPort() {
		return this.port;
	}
	
	/**
	 * Try to bind on port <b>this.port</b>.
	 * 
	 * @return true if operation was successful
	 */
	private synchronized boolean bindToPort() {
		try {
			channel.socket().bind(new InetSocketAddress(port));
		} catch (SocketException se) {
			return false;
		}
		return true;
	}

	private synchronized void listen() {
		if (DEBUG) {
			System.out.println("listening on port:" + this.port);
		}
		
		try {
			ByteBuffer buf = ByteBuffer.allocate(UDP_BUFFER_SIZE);
			while (true) {
				buf.clear();
				SocketAddress clientadr = channel.receive(buf);
				buf.flip();
				
				byte[] bytearr = new byte[buf.remaining()];
				buf.get(bytearr); 
				
				if (DEBUG) {
					System.out.println("MessageHandler - Incomming Message:");
					System.out.println("Address= " + clientadr.toString());
					System.out.print(  "Message= " + new String(bytearr));
				}
				
				Message incomingMsg = new Message(bytearr);
				if (DEBUG) {
					System.out.println("Parsed Message:");
					System.out.println("CMD= " + incomingMsg.getCommand());
					System.out.println("IP= " + incomingMsg.getIp());
					System.out.println("PORT= " + incomingMsg.getPort());
					System.out.println("ID= " + incomingMsg.getId());
					System.out.print  ("PAYLOAD= " + incomingMsg.getPayload());
				}
				

				switch (incomingMsg.getCommand()) {
				case Message.NEIGHBOR:
					action.addNeighbors(new NeighborMessage(incomingMsg).getNeighborList());
					break;
				case Message.ACK:
					//TODO
					break;
				case Message.PING:
					//TODO
					break;
				default:
					action.unknownType(incomingMsg);
					break;
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void sendMessage(Message m) {
		ByteBuffer buf = ByteBuffer.allocate(UDP_BUFFER_SIZE);
		buf.clear();
		
		buf.put(m.msgToByteArray());
		buf.flip();
		
		try {
			channel.send(buf, new InetSocketAddress(m.getIp(), m.getPort()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}
